package com.silence.gmall.handler;

import com.xxl.job.core.context.XxlJobHelper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 日志处理器类，提供日志记录的代理功能，支持增强日志方法并缓存Logger实例
 *
 * @Author silence
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoggerHandler {

    // 用于缓存Logger实例的Map，键为Class对象，值为对应的Logger实例
    private static final Map<Class<?>, Logger> LOGGER_CACHE = new ConcurrentHashMap<>();

    // 需要增强的日志方法列表
    private static final List<String> ENHANCE_METHOD_LIST = List.of("info", "debug", "warn", "error");

    /**
     * 获取指定类的Logger实例，如果缓存中不存在则创建新的Logger实例并存入缓存
     *
     * @param clazz 需要获取Logger的类
     * @return 对应的Logger实例
     */
    private static Logger getLogger(Class<?> clazz) {
        if (LOGGER_CACHE.containsKey(clazz)) {
            return LOGGER_CACHE.get(clazz);
        }
        Logger logger = LoggerFactory.getLogger(clazz);
        LOGGER_CACHE.put(clazz, logger);
        return logger;
    }

    /**
     * 创建Logger的代理实例
     *
     * @param clazz 需要创建代理的类
     * @return Logger的代理实例
     */
    public static Logger createLogger(Class<?> clazz) {
        Object proxy = Enhancer.create(Logger.class, new LoggerMethodInterceptor(clazz));
        return (Logger) proxy;

    }

    /**
     * Logger方法拦截器，用于拦截和增强日志方法 被代理的类
     *
     * @param clazz 构造函数
     */
    public record LoggerMethodInterceptor(Class<?> clazz) implements MethodInterceptor {

        /**
         * 方法拦截逻辑 检查是否是需要增强的日志方法且有参数
         *
         * @param o           代理对象
         * @param method      被拦截的方法
         * @param objects     方法参数
         * @param methodProxy 方法代理
         * @return 方法调用结果
         * @throws Throwable 异常
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            // 处理多个参数的情况
            if (ENHANCE_METHOD_LIST.contains(method.getName()) && objects != null && objects.length > 0) {
                Object[] args = new Object[0];
                if (objects.length > 1) {
                    // 超过3个参数时 logger 通过不定参数形式重载
                    if (method.getParameterCount() == 2 && method.getParameters()[1].getType().isArray()) {
                        args = (Object[]) objects[1];
                    } else {
                        args = new Object[objects.length - 1];
                        System.arraycopy(objects, 1, args, 0, args.length);
                    }
                }
                String temp = (String) objects[0];
                log(method.getName(), temp, args);
            }
            return null;
        }

        /**
         * 日志记录方法
         *
         * @param methodName 日志方法名称
         * @param str        日志消息
         * @param args       日志参数
         */
        public void log(String methodName, String str, Object... args) {
            Logger logger = getLogger(clazz);
            switch (methodName) {
                case "log" -> {
                    logger.info(str, args);
                    XxlJobHelper.log(str, args);
                }
                case "warn" -> {
                    logger.info(str, args);
                    XxlJobHelper.log(str, args);
                }
                case "debug" -> {
                    logger.debug(str, args);
                    XxlJobHelper.log(str, args);
                }
                case "error" -> {
                    logger.error(str, args);
                    logError(str, args);
                }
                default -> {
                    logger.info(str, args);
                    XxlJobHelper.log(str, args);
                }
            }
        }

        /**
         * 错误日志记录方法，处理异常情况
         *
         * @param str  日志消息
         * @param args 日志参数
         */
        public static void logError(String str, Object... args) {
            for (Object arg : args) {
                if (arg instanceof Throwable te) {
                    XxlJobHelper.log(te);
                    return;
                }
            }
            XxlJobHelper.log(str, args);
        }
    }
}